package cn.liuxiany.alibaba.gateway.handler;

import cn.liuxiany.alibaba.common.request.Result;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import reactor.core.publisher.Mono;

/**
 * 断路器fallback
 *
 * @author liuxiany
 * @date 2023/10/08
 */
@Slf4j
@RestController
public class FallbackController {

    @RequestMapping("/fallback")
    public Mono<Result<?>> fallback(ServerWebExchange exchange) {
        Exception exception = exchange.getAttribute(ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR);

        ServerWebExchange delegate = ((ServerWebExchangeDecorator) exchange).getDelegate();

        String url = delegate.getRequest().getURI().getPath();

        Result<?> result = null;
        if (exception instanceof org.springframework.cloud.gateway.support.NotFoundException) {
            // 当path匹配断言成功，但是根据serviceId寻找下游服务未找到时（即服务未启动）则抛出NotFoundException。当path未匹配到断言时进入globalExceptionHandler
            result = Result.failed("服务不可达");
        } else if (exception instanceof java.util.concurrent.TimeoutException) {
            // 当下游服务的执行之间超过了配置的timelimiter的阈值则会抛出timeoutException
            result = Result.failed("服务超时");
        } else if (exception instanceof io.github.resilience4j.circuitbreaker.CallNotPermittedException) {
            // 并不会捕获下游服务的异常。依据规则进入熔断的前提条件是捕获到了网关自身的异常。比如上面的NotFoundException和TimeoutException。因此下游服务可以使用全局异常进行自身异常的处理，而不影响网关的熔断。
            // 目前的熔断是服务级别的，而非是接口级别。当一个接口导致整个服务产生了熔断，则会影响到其他正常的接口。
            // todo 接口级别的熔断实现
            result = Result.failed("服务熔断");
        } else {
            result = Result.failed("服务错误");
        }

        log.error("[{}] circuit breaker result: {}", url, new Gson().toJson(result), exception);

        return Mono.just(result);
    }
}
